home *** CD-ROM | disk | FTP | other *** search
/ HPAVC / HPAVC CD-ROM.iso / TPTUTR~1.ZIP / PASCAL09.TXT < prev    next >
Text File  |  1996-03-21  |  22KB  |  592 lines

  1.                         Turbo Pascal for DOS Tutorial
  2.                              by Glenn Grotzinger
  3.                      Part 9 -- Applications Development
  4.              All parts copyright (c) 1995-6 by Glenn Grotzinger
  5.  
  6. Hello.  This time, I'm not going to go present the solution to part 8
  7. immediately, because I want to sue the part 8 problem to demonstrate
  8. applications development.  The basic reason I'm doing this is because
  9. programs should be designed and then programmed, normally, and not the
  10. other way around.  Normally in design, we have different tools available
  11. for us to use.  The ones I'm aware of are pseudocode and flowcharting.
  12. I can't cover flowcharting through text, but I can cover pseudocode.
  13.  
  14. What is pseudocode?
  15. ===================
  16. Pseudocode is basically English-like descriptions of what is going on.
  17. I gave you an example of it in part 8 (with capital letters for emphasis,
  18. as I will continue to do so) as it pertains to a good line parsing
  19. function.  We will go through and design a solution to part 8 in this
  20. part.
  21.  
  22. How to start out?
  23. =================
  24. It is good to start with the global way of things.  Let's start with a
  25. few facts we know from our knowledge from part 8.
  26.  
  27.         1) We're wanting listings of files.
  28.         2) We're wanting stats on the operating system as well as the
  29.            drive we access.
  30.  
  31. Knowing our purposes in these two statements, we know we need usage of
  32. the DOS or WinDOS unit.
  33.  
  34. Globally, by looking at the way we know the output needs to be, we can
  35. figure out that globally, the program is going to have to do the
  36. following:
  37.  
  38.         1) Write the ID of the program. {mydir by...}
  39.         2) Parse the command-line to determine what needs to be done.
  40.         3) Write resultant headers based on results in 2.
  41.         4) If successful, while there are valid files, list the files
  42.            as dictated in 2.
  43.         5) Show the global drive and operating system stats.
  44.  
  45. Starting to break down the global stuff and writing pseudocode
  46. ==============================================================
  47. We know this is the basic idea of things that we will have to do in
  48. order to complete this program.  We need to move from this down to
  49. the specific details.  Let's start with 1.
  50.  
  51. We need to write the ID of the program (Global 1).
  52. --------------------------------------------------
  53. Also, we need to look at factors for initialization code.  We need
  54. some filespec variable clear...Also we know we need to count files
  55. and dirs, as well as set a pause flag to our default (false).
  56.  
  57.         PROCEDURE WRITEID;
  58.            WRITE PROGRAM NAME AND AUTHOR NAME, COPYRIGHT INFO...
  59.            SET PAGE COUNT TO # OF LINES USED (page pausing!)
  60.         SET PAUSE TO FALSE, AND SET #DIRS AND #FILES TO 0.
  61.  
  62. Parse the command-line (Global 2).
  63. ----------------------------------
  64. I will start globally, and move down to specifics here.  Keep in mind
  65. that the functions said that path/filename and parameter should be
  66. interchangeable (Function 5 as specified in the problem).  Here, what
  67. seems logical is to use the most definable command-line parameter and
  68. eliminate things down to the least specific.  Here, the command params
  69. ? and P are the most specific while the path is the least specific.
  70.  
  71.         IF PARAMETERS > 2 THEN SHOW SOME HELP AND QUIT.
  72.         PULL ALL PARAMETERS INTO AN ARRAY (MAX 2).
  73.         FOR EACH PARAMETER GIVEN TO PROGRAM IN ARRAY
  74.          IF THE PARAMETER IS 2 CHARACTERS LONG AND THE
  75.            FIRST CHARACTER IS - OR /
  76.              IF SECOND CHARACTER OF PARAMETER IS ? SHOW SOME HELP AND QUIT.
  77.              IF SECOND CHARACTER IS P, SET A PAUSE FLAG TO TRUE.
  78.                ELSE SHOW SOME HELP AND QUIT.
  79.              END-IF.
  80.          ELSE SET FILESPEC TO THE COMMAND-LINE PARAMETER.
  81.          END-IF.
  82.         PARSE THE FILESPEC.
  83.  
  84. Now, this is a general description of what's going on in part 2 of our
  85. global listing we made.  Now for specifics here.  The only ones I see
  86. are showing the help and quitting, and parsing the filespec.
  87.  
  88. Show help and quit.
  89.         WRITE THE HELP.
  90.         SET PAGE COUNT TO NUMBER OF LINES USED (Page count for pause!)
  91.         HALT THE PROGRAM.
  92.  
  93. Parsing the filespec.  I gave you this pseudocode in part 8.
  94.  
  95. Write resultant headers based on 2. (Global 3)
  96. ----------------------------------------------
  97.         WRITE THE HEADER WITH PARSED PATH INTERPRETATION.
  98.  
  99. List files as dictated by filespec in 2. (Global 4)
  100. ---------------------------------------------------
  101. We know, basically, in the DIR implementation we want to list all files
  102. except ones with the volume lables.  We know the file constants are
  103. additive but to ease things in typing, we can go ahead and add them up.
  104. All files except volumeID in the constants is equal to $37.  Also, we will
  105. keep in mind the following points that were brought up in the directions.
  106.  
  107.         1) (Function 1) Show us for each filename on one line a size,
  108.            file attributes, date and time.
  109.         2) (Function 3) All integers or longints > 999 should be
  110.            delineated by commas, or periods, whichever you use.
  111.         3) (Function 4) Write r for read-only, a for archive, s for
  112.            system, and h for hidden.
  113.         4) If we need to pause, be sure to implement it!
  114.         5) Be sure to indicate if there are no files.
  115.  
  116.         START FILE LISTING.
  117.         WHILE WE STILL HAVE FILES
  118.            IF A FILENAME IS A DIRECTORY ($10) THEN
  119.               WRITE DIRECTORY NAME WIHT [DIR] DESIGNATION.
  120.               INCREMENT # OF DIRS BY 1.
  121.            ELSE
  122.               WRITE FILENAME, FILESIZE, DATE, TIME AND ATTRIBUTES.
  123.               INCREMENT # OF FILES BY 1.
  124.               INCREMENT SIZE OF FILES IN DIR BY SIZE OF THIS FILE.
  125.            END-IF.
  126.            INCREMENT PAGELENGTH BY 1.
  127.            IF PAGELENGTH > 23 AND PAUSE IS TRUE
  128.               WRITE PAUSE INDICATOR, READ FOR KEY, AND SET LINE
  129.               COUNTER BACK TO 0.
  130.            END-IF.
  131.         IF THERE IS A DOS ERROR OR THERE ARE NO DIRS AND NO FILES
  132.            WRITE THAT THERE ARE NO FILES THERE.
  133.  
  134. There's our global listing for part 4.  Now we need to consider the
  135. individual actions, basically, obtaining numbers with commas, the date,
  136. the time, and the file attributes.
  137.  
  138. Numbers with commas (Code explained later)
  139.         MAKE A STRING OUT OF THE NUMBER.
  140.         FIND LENGTH OF NUMBER.
  141.         WHILE THERE ARE MORE THAN 3 DIGITS TO CONSIDER
  142.              COUNT OFF 3 DIGITS FROM THE RIGHT TO THE LEFT.
  143.              PLACE A COMMA, AND SUBTRACT 3 DIGITS FROM LENGTH TOTAL.
  144.         END-WHILE.
  145.         COUNT OFF REST OF DIGITS.
  146.  
  147. The date and time.  Basically, the issue here is pulling the information
  148. for the date, except the year, where we must consider the last 2 digits
  149. instead of 4 digits.  In getting the last 2 digits, we also need to keep
  150. in mind that the year 2000 will be coming in 4 years...  With the
  151. time, it will be in military time, so we may recycle code, say from part
  152. 4 (It is always good to save code and copy so you do not have to
  153. invent the wheel and then turn around and invent it again...:)).  Note
  154. the repeated appearances of the strings "Make XXX a string." and "If
  155. XXX < 10, pad number with zero."  To pad a number, I mean, if I have
  156. 9, then padding it with a zero would make it 09.  In good programming
  157. planning, if repeated code happens, isolate that code as a function or
  158. procedure to save lines of code.  AT the end, you will see that I have
  159. done that.
  160.  
  161.         UNPACK THE DATE AND TIME FROM THE SYSTEM.
  162.         MAKE MONTH A STRING.
  163.         IF MONTH < 10, PAD NUMBER WITH 0.
  164.         MAKE DAY A STRING.
  165.         IF DAY < 10, PAD NUMBER WITH 0.
  166.         IF YEAR > OR = 2000
  167.            2YEAR IS YEAR - 2000
  168.         ELSE
  169.            2YEAR IS YEAR - 1900
  170.         MAKE 2YEAR A STRING.
  171.         IF YEAR < 10, PAD NUMBER WITH 0.
  172.         FINAL-FILE-DATE IS MONTH-DAY-2YEAR.
  173.                           (or DAY-MONTH-2YEAR, whichever you prefer)
  174.  
  175.         IF HOUR > OR = 12
  176.            HOUR IS HOUR - 12
  177.            MERIDIAN IS PM.
  178.         ELSE
  179.            MERIDIAN IS AM.
  180.         END-IF.
  181.         IF HOUR = 0 THEN HOUR = 12.
  182.         MAKE HOUR A STRING.
  183.         IF HOUR < 10, PAD STRING WITH 0.
  184.         MAKE MIN A STRING.
  185.         IF MIN < 10, PAD STRING WITH 0.
  186.         TIME IS HOUR:MINmeridian.
  187.  
  188. Get file attributes.  I gave hint #6 for this part to build your string
  189. and said I'd explain later.  A string in pascal (a pascal string),
  190. actually is stored using length + 1 bytes.  The first byte is a number
  191. representing the total length of the string.  The rest of it is the
  192. string.  Since we use a background of -'s, that's all we need to start
  193. the string from...Then use proper position to assign things...and again
  194. using the file attribute constants, remembering that they are additive.
  195. Starting from the largest to smallest...Say, we want to reassign the
  196. 3rd character of STR, we just say str[3] := 's' ro something like that.
  197.  
  198.         SET STRING TO ----.
  199.         IF FILEATTR >= $20 THEN
  200.            FILE HAS ARCHIVE BIT.
  201.            FILEATTR = FILEATTR - $20.
  202.         END-IF.
  203.         IF FILEATTR >= $04 THEN
  204.            FILE HAS SYSTEM BIT.
  205.            FILEATTR = FILEATTR - $04.
  206.         END-IF.
  207.         IF FILEATTR >= $02 THEN
  208.            FILE HAS HIDDEN BIT.
  209.            FILEATTR = FILEATTR - $02.
  210.         END-IF.
  211.         IF FILEATTR >= $01 THEN
  212.            FILE HAS READ-ONLY BIT.
  213.            FILEATTR = FILEATTR - $01.
  214.         END-IF.
  215.  
  216. Show the global stats and operating system info (Global 5)
  217. ----------------------------------------------------------
  218. Basically, this is a write operation.  But we need to know how to get
  219. the information...
  220.  
  221. Volume label.  Using the hint in the problem.
  222.  
  223.         SEARCH FOR FILE WITH VOLUMEID ATTRIBUTE IN ROOT DIR OF
  224.            DRIVE WE ARE ACCESSING.
  225.         IF THE FILE EXISTS, FILENAME IS THE VOLUMEID
  226.         ELSE LEAVE IT BLANK.
  227.  
  228. Total files and total dirs and total size of the files listed are
  229. simply writing variables (be sure to run these numbers through the
  230. comma delineator function).
  231.  
  232. Total size used on drive.  The drive is designated as a number, and the
  233. nice thing about our pase_filespec thing is that the first character
  234. always ends up being the drive letter of the drive we want to work with.
  235. So, to go from drive letter to number, we can always set a constant
  236. guide string (sort of like my suggestion in part 7 to do this for bases
  237. > 11 as to ease things) defined as: ABCDEFGHIJKLMNOPQRSTUVWXYZ.  I know
  238. this is probably overkill, but it will cover things OK so we don't have
  239. to write a large case statement.  Run this one through the delineator.
  240.  
  241.         DRIVE NUMBER IS LETTER POSITION IN CONSTANT STRING.
  242.         FREE-ON-DISK FOR DRIVE NUMBER.
  243.  
  244. Total size on drive.  Similar to total siz eused.  Uses constant guide
  245. string...also ran through delineator.
  246.  
  247.         DRIVE NUMBER IS LETTER POSITION IN CONSTANT STRING.
  248.         TOTAL-ON-DISK FOR DRIVE NUMBER.
  249.  
  250. DOS version.  Getting this is exactly like described in part 8.  Nothing
  251. out of the ordinary.
  252.  
  253. This finishes up my pseudocode description for the part 8 problem.  Now,
  254. for my best solution that I could come up, with the suggestion (probably
  255. could be faster if I didn't do it, but I went ahead and used the save
  256. format for the record as some help, and allowed for 999,999,999 as a
  257. possible total file size in the layout -- too big to really worry, though
  258. it probably slows it up a little...)
  259.  
  260. Keep in mind too, that this pseudocode was revised, after I found out that
  261. some of my original statements turned out to be wrong in my logic planning.
  262. It's OK to create incorrect pseudocode.  It is only a planning tool, and
  263. does not have to be correct.  The only thing anyone will really care about
  264. being correct is your final source code -- correct meaning that it works
  265. properly.
  266.  
  267. The source code for my implementation of MYDIR.
  268.  
  269. program part8; uses dos;
  270.  
  271.   { a dir command.  Supports command params /? and /P.
  272.     shows filename, filesizes with commas, time, date, attributes.
  273.     For total, shows drivesize in bytes, bytes used on drive.
  274.     Volume label of drive, total number of files, total numbers of
  275.     directories. }
  276.  
  277.   type
  278.     writerec = record     { format to write out the dirinfo }
  279.       filename: string[12];
  280.       filesize: string[11]; { largest size => 999,999,999 bytes }
  281.       filedate: string[8];
  282.       filetime: string[7];
  283.       fileattr: string[4];
  284.     end;
  285.  
  286.   var
  287.     dirinfo: searchrec;
  288.     writeinfo: writerec;
  289.     params: array[1..2] of string;
  290.     i: integer;
  291.     pause: boolean;
  292.     filespec: string;
  293.     dirs, files, totalsize: longint;
  294.     pagelen: integer;
  295.  
  296.   function parse_filespec(filename: string):string;
  297.  
  298.     const
  299.       all_files = $37;
  300.       { all file constants in base 16 added together but VolumeID }
  301.     var
  302.       dir: dirstr;   { required types for some of the commands as }
  303.       name: namestr; { defined in the DOS unit. }
  304.       ext: extstr;
  305.       attr: word;    { attribute must be a word. }
  306.       f: text;       { required for a command we had to assign file for }
  307.  
  308.     begin
  309.       filename := fexpand(filename);  { expand filename }
  310.       if filename[length(filename)] <> '\' then { if end not \ }
  311.         begin
  312.           assign(f, filename);
  313.           getfattr(f, attr);            { get the file attribute }
  314.           if (doserror = 0) and (attr = $10) then
  315.             filename := filename + '\'; { if it's a directory put \ }
  316.         end;
  317.       fsplit(filename, dir, name, ext); { split filename up. }
  318.       if name = '' then name := '*'; { if it's still a directory, }
  319.       if ext = '' then ext := '.*';  { specify ALL FILES }
  320.       parse_filespec := dir + name + ext;  { re-form filename }
  321.     end;
  322.  
  323.   function zero(innum: integer):string;
  324.     var
  325.       tstr: string[2];
  326.     begin
  327.       str(innum, tstr);
  328.       if innum < 10 then
  329.         tstr := '0' + tstr;
  330.       zero := tstr;
  331.     end;
  332.  
  333.   procedure showid;
  334.     begin
  335.       writeln('MYDIR (c) 1996 by Glenn Grotzinger.');
  336.       writeln;
  337.       pagelen := pagelen + 2;
  338.     end;
  339.  
  340.   procedure writeheader(filespec: string);
  341.     begin
  342.       writeln('File listing for: ', filespec);
  343.       writeln;
  344.       pagelen := pagelen + 2;
  345.     end;
  346.  
  347.   function number(n: longint):string;
  348.     var
  349.       i, j: integer;
  350.       s, r: string;
  351.       m: integer;
  352.     begin
  353.       str(n, s); { get the longint to a workable string }
  354.       r := '';   { r is a holding string for our delineated number }
  355.       i := length(s);
  356.       { set an integer to the length of our number.  We will be going
  357.       from the right end and moving to the left in placing our delin-
  358.       eations, and building r from the right to the left. }
  359.       if i > 3 then begin
  360.       while i > 3 do   { while we don't have 3 numbers left }
  361.         begin
  362.           for j := i downto i-2 do  { count off 3 digits and move to r}
  363.             r := s[j] + r;
  364.           r := ',' + r;  { write a comma or period to r }
  365.           i := i - 3;    { subtract 3 digits }
  366.         end;
  367.       for j := i downto 1 do
  368.       { we only have 3 digits left, or less now.  Just count off the
  369.         digits }
  370.         r := s[j] + r;
  371.       number := r;  { feed r to the function }
  372.       end
  373.     else
  374.       number := s;
  375.     end;
  376.  
  377.   procedure getdatetime(dirinfo: searchrec; var writeinfo: writerec);
  378.     { type uses datetime record }
  379.     var
  380.       dt: datetime;
  381.       a,b,c: string; { temp strings for use }
  382.       tmpyr: integer; { we need to define this one to do the year }
  383.     begin
  384.       unpacktime(dirinfo.time, dt); { unpack date and time }
  385.       { start with date }
  386.       { month }
  387.       a := zero(dt.month);
  388.       { day -- same as month }
  389.       b := zero(dt.day);
  390.       { year -- reported as 4 digits.  We need to get it down to 2. }
  391.       { keep in mind the valid range that TP will report year. }
  392.       if dt.year > 1999 then      { if year 2000 or above }
  393.         tmpyr := dt.year - 2000
  394.       else                        { else would be before 2000 }
  395.         tmpyr := dt.year - 1900;
  396.       { now that we have our last 2 digit year, perform similar to day
  397.         & month }
  398.       c := zero(tmpyr);
  399.       writeinfo.filedate := a + '-' + b + '-' + c; { set final date }
  400.       { now start with the time -- it's reported as military time --
  401.         we need to deal with it as such }
  402.       { military time determination }
  403.       if dt.hour >= 12 then
  404.         begin
  405.           dt.hour := dt.hour - 12;
  406.           c := 'pm';
  407.         end
  408.       else
  409.         c := 'am';
  410.       if dt.hour = 0 then
  411.         dt.hour := 12;
  412.       { hours -- deal with as we did days now }
  413.       a := zero(dt.hour);
  414.       { minutes }
  415.       b := zero(dt.min);
  416.       writeinfo.filetime := a + ':' + b + c;
  417.     end;
  418.  
  419.   function getfileattr(dirinfo: searchrec):string;
  420.     var
  421.       left: word;
  422.       str: string;
  423.  
  424.     begin
  425.       str := '----';
  426.       left := dirinfo.attr;
  427.       if left >= $20 then  { archive file }
  428.         begin
  429.           str[2] := 'a';
  430.           left := left - $20;
  431.         end;
  432.       if left >= $04 then  { system file }
  433.         begin
  434.           str[3] := 's';
  435.           left := left - $04;
  436.         end;
  437.       if left >= $02 then  { hidden file }
  438.         begin
  439.           str[4] := 'h';
  440.           left := left - $02;
  441.         end;
  442.       if left >= $01 then  { read-only file }
  443.         begin
  444.           str[1] := 'r';
  445.           left := left - $01;
  446.         end;
  447.       getfileattr := str;
  448.     end;
  449.  
  450.   procedure writefinfo(dirinfo: searchrec;writeinfo: writerec);
  451.     var
  452.       i: integer;
  453.     begin
  454.       with writeinfo do
  455.         begin
  456.           filename := dirinfo.name;
  457.           filesize := number(dirinfo.size);
  458.           getdatetime(dirinfo, writeinfo);
  459.           fileattr := getfileattr(dirinfo);
  460.           write(filename);
  461.           for i := length(filename) to 12 do
  462.             write(' ');
  463.           writeln(filesize:12, filedate:12, filetime:12, fileattr:12);
  464.         end;
  465.     end;
  466.  
  467.   function getvolumeID(drive: char):string;
  468.     { volume label exists as a file in the root directory with a special
  469.       attribute called VolumeID and the name of the file is the volume
  470.       label }
  471.     var
  472.       fstr: string;
  473.       dinfo: searchrec;
  474.     begin
  475.       fstr := drive + ':\*.*';
  476.       findfirst(fstr, VolumeID, dinfo);
  477.       if doserror = 0 then  { Volume label exists so...}
  478.          getvolumeID := dinfo.name
  479.       else
  480.          getvolumeID := ''; { leave it blank if no volume label }
  481.     end;
  482.  
  483.   procedure showhelp;
  484.     begin
  485.       writeln('Help:');
  486.       writeln('  MYDIR <filespec> /<parameters>');
  487.       writeln('  filespec is the filename/dirname(s) we want to list.');
  488.       writeln('  parameters are ? or P (case insensitive)');
  489.       writeln('       ? --> This help.');
  490.       writeln('       P --> pause on screen page.');
  491.       halt(1);
  492.    end;
  493.  
  494.   function bytesfree(drive: char):longint;
  495.     const
  496.       guide = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  497.     var
  498.       dno: integer;
  499.     begin
  500.       dno := pos(upcase(drive), guide);
  501.       bytesfree := diskfree(dno);
  502.     end;
  503.  
  504.   function bytesthere(drive: char):longint;
  505.     { in TP, this won't work if you have a partition > 1 gigabyte }
  506.     const
  507.       guide = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  508.     var
  509.       dno: integer;
  510.     begin
  511.       dno := pos(upcase(drive), guide);
  512.       bytesthere := disksize(dno);
  513.     end;
  514.  
  515.   begin
  516.     { initialization code }
  517.     showid; { should be the first thing we do }
  518.     pause := false;
  519.     filespec := '';
  520.     totalsize := 0;
  521.     dirs := 0;
  522.     files := 0;
  523.     { check # of parameters }
  524.     if paramcount > 2 then
  525.       showhelp;
  526.     { pull in parameters }
  527.     for i := 1 to paramcount do
  528.       params[i] := paramstr(i);
  529.     { check 'em for ? and P parameters, and filespec }
  530.     for i := 1 to paramcount do
  531.       if (length(params[i]) = 2) and
  532.          (params[i][1] in ['-','/']) then
  533.          case upcase(params[i][2]) of
  534.            '?': showhelp;
  535.            'P': pause := true;
  536.          else
  537.            showhelp;
  538.          end
  539.       else
  540.         filespec := params[i];
  541.     filespec := parse_filespec(filespec);
  542.  
  543.     { go into start }
  544.     writeheader(filespec);
  545.     findfirst(filespec, $37, dirinfo);
  546.     while doserror = 0 do
  547.       begin
  548.         if dirinfo.attr = $10 then
  549.           begin
  550.             write(dirinfo.name);
  551.             for i := length(dirinfo.name) to 12 do
  552.               write(' ');
  553.             writeln('[DIR]':49);
  554.             inc(dirs);
  555.           end
  556.         else
  557.           begin
  558.             writefinfo(dirinfo, writeinfo);
  559.             inc(files);
  560.             totalsize := totalsize + dirinfo.size;
  561.           end;
  562.         inc(pagelen);
  563.         if (pagelen > 23) and (pause) then
  564.           begin
  565.             write('--Pause--');
  566.             readln;
  567.             pagelen := 0;
  568.           end;
  569.         findnext(dirinfo);
  570.       end;
  571.     if (doserror in [1..17]) or ((dirs = 0) and (files = 0)) then
  572.       writeln('No files found.');
  573.     writeln;
  574.     writeln('Volume label: ', getvolumeid(filespec[1]), 'Total Files: ':20,
  575.              number(files), 'Total Dirs: ':20, number(dirs));
  576.     writeln('DOS Version: ', lo(dosversion), '.', hi(dosversion));
  577.     writeln;
  578.     writeln(number(totalsize), ' bytes.');
  579.     writeln(number(bytesfree(filespec[1])), ' bytes free out of ',
  580.             number(bytesthere(filespec[1])), ' total bytes.');
  581.  
  582.   end.
  583. Next Time
  584. =========
  585. We will cover reading and writing from binary files in part 10, as well
  586. as use of units, overlays, and include files in programming.  Part 10
  587. will be a long program, which will stress the ideas represented here
  588. mainly, but will also involve material in part 10.  It will be a tedious
  589. one, but does not require a lot of programming knowledge to complete.
  590. This problem will also be set up for a programming contest.  If there
  591. are any comments, please write ggrotz@2sprint.net.
  592.